package security_center

import (
	"context"
	"encoding/json"
	"fmt"
	log "github.com/sirupsen/logrus"
	"qabx.net/common"
	report "qabx.net/safe-app-center/api/report"
	"qabx.net/safe-app-center/api/security_center_api"
	report_client "qabx.net/safe-app-center/pkg/client/report"
	"qabx.net/safe-app-center/pkg/config/constants"
	"qabx.net/safe-app-center/pkg/model"
	"qabx.net/safe-app-center/pkg/monitor"
	"qabx.net/safe-app-center/pkg/utils"
	"strconv"
	"strings"
	"time"
)

type SecurityCenterServer struct {
	security_center_api.UnimplementedSecurityCenterApiServiceServer
}

func (s *SecurityCenterServer) PushTrafficLog(ctx context.Context, request *security_center_api.PushTrafficLogRequest) (resp *security_center_api.PushTrafficLogResponse, err error) {
	resp = &security_center_api.PushTrafficLogResponse{
		Code:    201,
		Message: "",
	}

	//request.Http
	trafficLog := &report.TrafficLog{}
	if request.TrafficType == 1 {
		trafficLog = HttpAdapter(request.Http)
		info := model.TblDeviceInfo{
			DeviceMac:  trafficLog.DeviceMac,
			DeviceName: request.Http.OsInfo,
		}
		go func() {
			//记录设备mac与设备信息
			_, _ = info.AddAndUpdateDeviceInfoByMac()
		}()
	} else if request.TrafficType == 2 {
		trafficLog = HttpsAdapter(request.Https)
	} else if request.TrafficType == 3 {
		trafficLog = DnsAdapter(request.Dns)
	} else {
		log.Errorf("pushTrafficLog trafficType unknown:%d", request.TrafficType)
		err = fmt.Errorf("request trafficType unknown:%d", request.TrafficType)
		return
	}

	marshal, _ := json.Marshal(trafficLog)
	log.Infof("get traffic log:%s", string(marshal))
	go HandleTraffic(trafficLog, request.AreaNo)
	resp.Code = 200
	return
}

func HandleTraffic(trafficLog *report.TrafficLog, areaNo string) {
	response, err := report_client.CallUploadTrafficLog(trafficLog)
	if err != nil {
		log.Errorf("call upload trafficLog failed:%s", err.Error())
		return
	}

	if response.Hit == 0 {
		return
	} else {
		cbc, err := utils.DecodeCBC(response.Message, "IK30T3ATKG57VDOB")
		if err != nil {
			log.Errorf("alarmlog decode failed:%s", err.Error())
			return
		}
		alarmLog := &report.AlarmLog{}
		err = json.Unmarshal([]byte(cbc), &alarmLog)
		if err != nil {
			log.Errorf("alarmlog json unmarshal failed:%s", err.Error())
			return
		}

		if alarmLog.AlarmType == "恶意网站" {
			if alarmLog.MaliciousAccessLog == nil {
				log.Errorf("alarmlog MaliciousAccessLog is empty")
				return
			}

			maliciousTime := time.UnixMicro(trafficLog.Tstamp).Format("2006-01-02 15:04:05")
			maliciousLog := &model.TblMaliciousLog{
				MaliciousType:     alarmLog.MaliciousAccessLog.MaliciousType,
				MaliciousTypeDesc: alarmLog.MaliciousAccessLog.MaliciousTypeDesc,
				HitType:           "1",
				MaliciousTime:     maliciousTime,
				Url:               alarmLog.MaliciousAccessLog.Domain,
				Ip:                alarmLog.MaliciousAccessLog.DestIP,
				MaliciousDesc:     fmt.Sprintf("您的设备正在访问-%s网址", alarmLog.MaliciousAccessLog.MaliciousTypeDesc),
				MaliciousTypeId:   constants.MALICIOUS_TYPE_MAP[alarmLog.MaliciousAccessLog.MaliciousType],
				ConfigType:        3,
			}
			if maliciousLog.MaliciousTypeId == 0 {
				maliciousLog.MaliciousTypeId = 5
			}

			tblGateway, tblDevice := getGatewayDeviceInfo(trafficLog.GateWayMAC, trafficLog.PPPOE, trafficLog.DeviceMac, areaNo)
			maliciousLog.GatewayId = tblGateway.GatewayId
			maliciousLog.DeviceId = tblDevice.DeviceId

			addBreachScope(maliciousLog)
			err := maliciousLog.AddMaliciousLog()
			if err != nil {
				log.Errorf("db insert failed: malicious addMalicious failed:%s", err.Error())
				return
			}

			if maliciousLog.MaliciousScope >= 90 {
				userGateway := model.TblUserGatewayRel{}
				userGateway.GatewayId = maliciousLog.GatewayId
				userId, err := userGateway.GetUserIdByGateway()
				if err != nil {
					log.Errorf("user not found: malicious GetUserIdByGateway failed:%s", err.Error())
					return
				}

				//如果分数大于90则直接加入封堵规则中 域名分值的判断
				strategy := model.TblBlockStrategy{}
				strategy.AreaNo = areaNo
				strategy.BlockData = maliciousLog.Url
				strategyId, err := strategy.AddBlockStrategy()
				if err != nil {
					log.Errorf("db insert failed: malicious AddBlockStrategy failed:%s", err.Error())
					return
				}
				group := model.TblBlockGroup{}
				group.BlockStrategyId = strategyId
				group.UserId = userId
				group.BlockType = 3
				group.GatewayId = maliciousLog.GatewayId
				group.DeviceId = maliciousLog.DeviceId
				group.IsOpen = 1
				blockGroupId, err := group.AddBlockGroup()
				if err != nil {
					log.Errorf("db insert failed: malicious AddBlockGroup failed:%s", err.Error())
					return
				}

				circle := model.TblBlockCircle{}
				circle.BlockGroupId = blockGroupId
				circle.CircleWeek = "1,2,3,4,5,6,7"
				circle.CircleHour = "00,23"
				circle.CircleMinute = "00,59"
				err = circle.AddBlockCircle()
				if err != nil {
					log.Errorf("db insert failed: malicious AddBlockCircle failed:%s", err.Error())
					return
				}

				//redis 队列存储
				blockGatewayKey := fmt.Sprintf("block_gateway")
				utils.TTLOperator.LPushList(blockGatewayKey, strconv.Itoa(maliciousLog.GatewayId)+"-"+strconv.Itoa(maliciousLog.DeviceId)+"-"+strconv.Itoa(userId))

			}

			return
		} else if alarmLog.AlarmType == "失陷" {
			if alarmLog.BreachDeviceLog == nil {
				log.Errorf("alarmlog BreachDeviceLog is empty")
				return
			}

			maliciousTime := time.UnixMicro(alarmLog.BreachDeviceLog.Tstamp).Format("2006-01-02 15:04:05")
			maliciousLog := &model.TblMaliciousLog{
				MaliciousType:     alarmLog.BreachDeviceLog.BreachDetail.VirusName,
				MaliciousTypeDesc: alarmLog.BreachDeviceLog.BreachDetail.DetailInformation,
				HitType:           "2",
				MaliciousTime:     maliciousTime,
				Url:               alarmLog.BreachDeviceLog.BreachDetail.Domain,
				Ip:                alarmLog.BreachDeviceLog.BreachDetail.DestIP,
				MaliciousDesc:     fmt.Sprintf("您的设备正在受到-%s的威胁，请尽快下发阻断规则，离线该设备进行并在安全模式下进行全盘杀毒", alarmLog.BreachDeviceLog.BreachDetail.VirusName),
				MaliciousTypeId:   constants.MALICIOUS_TYPE_MAP[alarmLog.MaliciousAccessLog.MaliciousType],
				ConfigType:        2,
			}
			if maliciousLog.MaliciousTypeId == 0 {
				maliciousLog.MaliciousTypeId = 5
			}
			tblGateway, tblDevice := getGatewayDeviceInfo(trafficLog.GateWayMAC, trafficLog.PPPOE, trafficLog.DeviceMac, areaNo)

			maliciousLog.GatewayId = tblGateway.GatewayId
			maliciousLog.DeviceId = tblDevice.DeviceId
			err := maliciousLog.AddMaliciousLog()
			if err != nil {
				log.Errorf("db insert failed: malicious addMalicious failed:%s", err.Error())
				return
			}
			return

		} else {
			log.Errorf("alarmLog alarmType unknown:%s", alarmLog.AlarmType)
		}
	}
}

func addBreachScope(maliciousLog *model.TblMaliciousLog) {
	scopeKey := fmt.Sprintf("bScope")
	targetKey := ""
	if maliciousLog.Url != "" {
		targetKey = maliciousLog.Url
	} else {
		targetKey = maliciousLog.Ip
	}

	isExist, scopeStr := utils.TTLOperator.Hget(scopeKey, targetKey)
	if isExist && scopeStr != "" {
		scopeInt, err := strconv.Atoi(scopeStr)
		if err != nil {
			maliciousLog.MaliciousScope = 60
			log.Errorf("strconv err:%s", err.Error())
			return
		}
		maliciousLog.MaliciousScope = scopeInt
	} else {
		maliciousLog.MaliciousScope = 60
	}

}

func getGatewayDeviceInfo(gatewayMac, pppoeName, deviceMac, areaNo string) (*model.TblGateway, *model.TblDevice) {
	var err error

	deviceMac = strings.ToUpper(deviceMac)
	if pppoeName == "" {
		tblGateway := &model.TblGateway{}
		tblDevice := &model.TblDevice{}
		return tblGateway, tblDevice
	}

	tblGateway := &model.TblGateway{
		GatewayMac: gatewayMac,
		PppoeName:  pppoeName,
		AreaNo:     areaNo,
	}
	tblGateway, err = tblGateway.GetGatewayByPppoeNameAndAddGateway()
	if err != nil {
		log.Errorf("db query failed: tbl_gateway GetGatewayByMacAndAddGateway failed:%s", err.Error())
		//return
	}

	tblDevice := &model.TblDevice{
		DeviceMac: deviceMac,
	}
	tblDevice, err = tblDevice.GetDeviceAndAddDevice(tblGateway.PppoeName, tblGateway.GatewayId, areaNo)
	if err != nil {
		log.Errorf("db query failed: tbl_device GetDeviceByMac failed:%s", err.Error())
		//return
	}
	return tblGateway, tblDevice
}

func DnsAdapter(req *security_center_api.DnsTrafficReq) *report.TrafficLog {
	sourcePort, _ := strconv.Atoi(req.UserPort)
	destPort, _ := strconv.Atoi(req.AppServerPort)
	quintet := &report.Quintet{
		SourceIP:   req.UserIpv4,
		SourcePort: int32(sourcePort),
		DestIP:     req.AppServerIpIpv4,
		DestPort:   int32(destPort),
		Protocol:   "TCP",
	}

	dns := &report.DNS{
		RequestDNS:   req.DomainName,
		ResopnseIP:   req.AppServerIpIpv4,
		ResopnseType: "IPV4",
		DeviceType: &report.DeviceType{
			Brand: "",
			Model: "",
			Dtype: "",
		},
	}

	trafficLog := &report.TrafficLog{
		PPPOE:       req.Id,
		GateWayMAC:  req.DstMac,
		LOID:        "",
		MessageType: "0",
		Quintet:     quintet,
		Tstamp:      req.ProcedureStartTime,
		DNS:         dns,
		HTTP:        nil,
		DeviceMac:   req.SrcMac,
	}

	return trafficLog
}

func HttpAdapter(req *security_center_api.HttpTrafficReq) *report.TrafficLog {
	sourcePort, _ := strconv.Atoi(req.UserPort)
	destPort, _ := strconv.Atoi(req.AppServerPort)
	quintet := &report.Quintet{
		SourceIP:   req.UserIpv4,
		SourcePort: int32(sourcePort),
		DestIP:     req.AppServerIpIpv4,
		DestPort:   int32(destPort),
		Protocol:   "TCP",
	}

	http := &report.HTTP{
		Host: req.Host,
		URI:  req.Uri,
		DeviceType: &report.DeviceType{
			Brand: "",
			Model: "",
			Dtype: "",
		},
	}

	trafficLog := &report.TrafficLog{
		PPPOE:       req.Id,
		GateWayMAC:  req.DstMac,
		LOID:        "",
		MessageType: "1",
		Quintet:     quintet,
		Tstamp:      req.ProcedureStartTime,
		DNS:         nil,
		HTTP:        http,
		DeviceMac:   req.SrcMac,
	}

	return trafficLog
}

func HttpsAdapter(req *security_center_api.HttpsTrafficReq) *report.TrafficLog {
	sourcePort, _ := strconv.Atoi(req.UserPort)
	destPort, _ := strconv.Atoi(req.AppServerPort)
	quintet := &report.Quintet{
		SourceIP:   req.UserIpv4,
		SourcePort: int32(sourcePort),
		DestIP:     req.AppServerIpIpv4,
		DestPort:   int32(destPort),
		Protocol:   "TCP",
	}

	http := &report.HTTP{
		Host: req.SNI,
		URI:  "?",
		DeviceType: &report.DeviceType{
			Brand: "",
			Model: "",
			Dtype: "",
		},
	}

	trafficLog := &report.TrafficLog{
		PPPOE:       req.Id,
		GateWayMAC:  req.DstMac,
		LOID:        "",
		MessageType: "1",
		Quintet:     quintet,
		Tstamp:      req.ProcedureStartTime,
		DNS:         nil,
		HTTP:        http,
		DeviceMac:   req.SrcMac,
	}

	return trafficLog
}

func (s *SecurityCenterServer) PushAppAnalyzeLog(ctx context.Context, request *security_center_api.PushAppAnalyzeLogRequest) (response *security_center_api.PushAppAnalyzeLogResponse, err error) {
	response = &security_center_api.PushAppAnalyzeLogResponse{
		Code:    0,
		Message: "",
	}

	reqBytes, _ := json.Marshal(request)
	log.Infof("push app analyze log req:%s", string(reqBytes))

	go saveAppAnalyzeLog(request)
	response = &security_center_api.PushAppAnalyzeLogResponse{
		Code:    1,
		Message: "ok",
	}
	return
}

func saveAppAnalyzeLog(request *security_center_api.PushAppAnalyzeLogRequest) {
	_, device := getGatewayDeviceInfo("", request.PppoeName, request.DeviceMac, request.AreaNo)

	var deviceId int
	if device != nil {
		deviceId = device.DeviceId
	}

	tblAppAnalyze := &model.TblAppAnalyze{
		DeviceId:  deviceId,
		AppId:     int(request.AppId),
		StartTime: request.UseStartTime,
		EndTime:   request.UseEndTime,
	}
	err := tblAppAnalyze.AddAppAnalyzeLog()
	if err != nil {
		log.Errorf("db insert error: addAppanalyzeLog error:%s", err.Error())
		return
	}
}

func (s *SecurityCenterServer) PushInterceptLog(ctx context.Context, request *security_center_api.PushInterceptLogRequest) (response *security_center_api.PushInterceptLogResponse, err error) {
	response = &security_center_api.PushInterceptLogResponse{
		Code:    0,
		Message: "",
	}

	go saveInterceptLog(request)
	response = &security_center_api.PushInterceptLogResponse{
		Code:    1,
		Message: "ok",
	}
	return
}

func (s *SecurityCenterServer) GetFeatureList(ctx context.Context, request *security_center_api.FeatureListRequest) (response *security_center_api.FeatureListResponse, err error) {

	featureVersionKey := fmt.Sprintf("%s", constants.REDIS_KEY_FEATURE_VERSION)

	redisVersion, err := utils.TTLOperator.LIndex(featureVersionKey, 0)

	version, _ := strconv.ParseFloat(fmt.Sprint(redisVersion), 64)
	reqVersion, _ := strconv.ParseFloat(request.GetVersion(), 64)
	if request.GetVersion() == "" || reqVersion < version {
		//从redis获取feature特征库内容
		featureContent, err := utils.GetFeatureContent(redisVersion)
		if err != nil {
			log.Errorf("GetFeatureList error:%s", err.Error())
			return nil, err
		}
		//将特征库字符串转换成protobuf格式
		response, _ = utils.GetFeatureList(featureContent)
		return response, nil
	}

	return nil, nil

}

func (s *SecurityCenterServer) HeartBeat(ctx context.Context, request *security_center_api.HeartBeatRequest) (response *security_center_api.HeartBeatResponse, err error) {
	now := time.Now()
	HeartBeatKey := fmt.Sprintf("%s:%v:%v", constants.REDIS_KEY_HEART_BEAT, now.Format("20060102"), request.DeviceID)
	featureVersionKey := fmt.Sprintf("%s", constants.REDIS_KEY_FEATURE_VERSION)

	strHeartTime := fmt.Sprintf("%v", request.HeartTime)
	//加入redis 走自动任务插入心跳包数据
	err = utils.TTLOperator.ZAdd(HeartBeatKey, float64(now.Unix()), strHeartTime)
	_ = utils.TTLOperator.Expire(HeartBeatKey, constants.TTL_HEART_BEAT)
	if err != nil {
		log.Errorf("redis lpush error: heart_beat_key error:%s", err.Error())
		return nil, err
	}
	version, err := utils.TTLOperator.LIndex(featureVersionKey, 0)

	if err != nil {
		log.Errorf("redis error: heartBeat error:%s", err.Error())
		return nil, err
	}

	heartCronTime, netflowOfflineTime := securityCenterRedis()

	response = &security_center_api.HeartBeatResponse{
		Version:            version,
		ServerTime:         now.Unix(),
		NetflowOfflineTime: netflowOfflineTime,
		HeartCronTime:      heartCronTime,
	}
	deviceId := common.Int64ToString(request.DeviceID)
	monitor.NumNetflowHeartBeat.WithLabelValues(deviceId).Inc()
	monitor.NetFlowOnlineGateWayNum.WithLabelValues(deviceId).Set(float64(request.OnlineGateWayNum))
	monitor.NetFlowOnlineDeviceNum.WithLabelValues(deviceId).Set(float64(request.OnlineDeviceNum))
	monitor.OnlineNetflow.WithLabelValues(deviceId).Set(1)
	monitor.TodayLastHeartbeat.WithLabelValues(deviceId).Set(float64(request.HeartTime))
	return response, nil
}

func (s *SecurityCenterServer) HeartBeatDeviceRegister(ctx context.Context, request *security_center_api.HeartBeatDeviceRegisterRequest) (response *security_center_api.HeartBeatDeviceRegisterResponse, err error) {

	device := &model.HeartDevice{}
	device.DeviceId = request.DeviceId
	device.CreateTime = time.Now().Format("2006-01-02 15:04:05")
	device.UpdateTime = time.Now().Format("2006-01-02 15:04:05")
	//存redis 永久
	heartBeatDeviceKey := fmt.Sprintf("%s", constants.REDIS_KEY_HEART_BEAT_DEVICE)
	decStr, err := json.Marshal(device)
	response = &security_center_api.HeartBeatDeviceRegisterResponse{}
	if err != nil {
		response.Code = 201
		response.Message = "fail"
		log.Errorf("heartBeatDeviceRegister error:%s", err.Error())
		return nil, err
	}
	deviceId := fmt.Sprintf("%v", device.DeviceId)
	utils.TTLOperator.Hset(heartBeatDeviceKey, deviceId, string(decStr))

	//新增prometheus监控
	monitor.OnlineNetflow.WithLabelValues(deviceId).Set(0)
	monitor.TodayLastHeartbeat.WithLabelValues(deviceId).Set(0)
	monitor.NetFlowOnlineDeviceNum.WithLabelValues(deviceId).Set(0)
	monitor.NetFlowOnlineGateWayNum.WithLabelValues(deviceId).Set(0)
	monitor.NumNetflowHeartBeat.WithLabelValues(deviceId).Set(0)

	response.Code = 200
	response.Message = "success"
	return
}

func saveInterceptLog(request *security_center_api.PushInterceptLogRequest) {
	_, device := getGatewayDeviceInfo("", request.Account, request.DeviceMac, request.AreaNo)

	var deviceId int
	if device != nil {
		deviceId = device.DeviceId
	}

	interceptId, err := strconv.Atoi(request.RuleId)
	if err != nil {
		log.Errorf("strconv atoi error:interceptId conv failed:%s", err.Error())
		return
	}
	tblMaliciousInterceptLog := &model.TblMaliciousInterceptLog{
		GatewayId:       0,
		DeviceId:        deviceId,
		SourceIp:        request.SrcIp,
		SourcePort:      request.SrcPort,
		DestIp:          request.DestIp,
		DestPort:        request.DestPort,
		InterceptTime:   request.LogTime,
		Url:             request.Url,
		UserAgent:       request.UserAgent,
		Referer:         request.Referer,
		InterceptRoleId: interceptId,
	}

	err = tblMaliciousInterceptLog.AddMaliciousInterceptLog()
	if err != nil {
		log.Errorf("db insert error: AddMaliciousInterceptLog error:%s", err.Error())
		return
	}
}

func (s *SecurityCenterServer) GetSecurityCenterConfig(ctx context.Context, request *security_center_api.SecurityCenterConfigRequest) (response *security_center_api.SecurityCenterConfigResponse, err error) {

	response = &security_center_api.SecurityCenterConfigResponse{}
	//mapKey := fmt.Sprintf("edgeMap:%s", request.GetProvince())

	heartCronTime, netflowOfflineTime := securityCenterRedis()

	response.NetflowOfflineTime = netflowOfflineTime
	response.HeartCronTime = heartCronTime

	featureVersionKey := fmt.Sprintf("%s", constants.REDIS_KEY_FEATURE_VERSION)

	version, err := utils.TTLOperator.LIndex(featureVersionKey, 0)
	if err != nil {
		return nil, err
	}

	response.FeatureVersion = version

	return response, nil
}

func (s *SecurityCenterServer) DeviceVerify(ctx context.Context, request *security_center_api.DeviceVerifyRequest) (response *security_center_api.DeviceVerifyResponse, err error) {
	heartBeatDeviceKey := fmt.Sprintf("%s", constants.REDIS_KEY_HEART_BEAT_DEVICE)
	isExist, deviceStr := utils.TTLOperator.Hget(heartBeatDeviceKey, fmt.Sprintf("%v", request.GetDeviceId()))

	response = &security_center_api.DeviceVerifyResponse{}
	if isExist && deviceStr != "" {
		response.Code = 200
		response.Message = "success"
	} else {
		response.Code = 400
		response.Message = "fail"
	}
	return response, nil
}

func securityCenterRedis() (heartCronTime, netflowOfflineTime int64) {

	redisClient := utils.TTLOperator
	heartCronTimeKey := fmt.Sprintf("%s:%s", constants.SECUIRTY_CENTER, constants.HEART_CRON_TIME)
	netflowOfflineTimeKey := fmt.Sprintf("%s:%s", constants.SECUIRTY_CENTER, constants.NETFLOW_OFFLINE_TIME)

	netFlowOffLineTimeStr, _ := redisClient.Get(netflowOfflineTimeKey)
	heartCronTimeStr, _ := redisClient.Get(heartCronTimeKey)

	if len(heartCronTimeStr) > 0 {
		heartCronTime, _ = common.StringToInt64(heartCronTimeStr)
	} else {
		heartCronTime = 60
	}

	if len(netFlowOffLineTimeStr) > 0 {
		netflowOfflineTime, _ = common.StringToInt64(netFlowOffLineTimeStr)
	} else {
		netflowOfflineTime = 300
	}
	return
}

//type AutoUploadHeartBeat struct {
//}
//
//func (AutoUploadHeartBeat) Run() {
//	HeartBeatKey := fmt.Sprintf("%s", constants.REDIS_KEY_HEART_BEAT)
//	heartLen, _ := utils.TTLOperator.LLen(HeartBeatKey)
//	if heartLen > 0 {
//		page := math.Ceil(float64(heartLen) / 200)
//		for i := 0; i < int(page)*200; i++ {
//			taskStr, _ := utils.TTLOperator.RPop(HeartBeatKey)
//
//			if taskStr != "" {
//				//插入心跳数据
//				request := &security_center_api.HeartBeatRequest{}
//				heartDeviceLog := &model.HeartDeviceLog{}
//				err := proto.Unmarshal([]byte(taskStr), request)
//				heartDeviceLog.DeviceId = request.GetDeviceID()
//				heartDeviceLog.HeartTime = request.GetHeartTime()
//				err = heartDeviceLog.AddHeartDeviceLog()
//				if err != nil {
//					log.Errorf("AutoUploadHeartBeat error db insert error: AddHeartDeviceLog error:%s", err.Error())
//				}
//			}
//		}
//	}
//
//}
